라이브러리처럼 작동하게 만들기 위해 다른 프로젝트에서 접근할 수 있도록 접근 포인트를 지정해줘야한다. package.json
에 main
필드 값을 lib/index.js
로 지정해주자. 번들러를 통해 생성된 파일 경로가 될 것이다.
그리고 파일 하나를 더 추가해야하는데, 이 파일은 우리가 만든 컴포넌트 라이브러리에서 어떤 부분이 노출이 되는지 결정한다. src/index.js
을 만들고 만들었던 버튼 컴포넌트를 export
해보자
export {default as Button } from "components/Button";
방금 만든 src/index.js
파일과 lib/index.js
파일은 각각 우리가 만드는 라이브러리의 입/출력이라고 볼 수 있다.
Rollup을 이용하여 번들링 할 것이다. 설치해주자
npm i -D rollup rollup-plugin-commonjs rollup-plugin-babel
Rollup 설정 파일을 만들자. rollup.config.js
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import packageJSON from "./package.json";
const input = "./src/index.js";
export default [
// CommonJS
{
input,
output: {
file: packageJSON.main,
format: "cjs"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
commonjs()
]
}
];
Rollup설정 파일을 보면 input
과 output
필드가 각각 src/index.js
와 lib/index.js
를 번들링 하면 된다고 경로를 지정해주는 부분이다.
이제 package.json
에 빌드 스크립트를 추가하자.
"build": "rollup -c"
그냥 빌드를 해보면 경고 메세지가 두개 뜬다.
(!) Unresolved dependencies
...
(!) Unused external imports
Rollup은 상대경로의 모듈 ID만 해결한다. 그러니까 import X from 'Y'
같은 import구문은 제대로 작동하지 않는다는 뜻이다. 이런 것들은 runtime시 포함되는 외부 dependency로 취급한다. 물론 유저가 Y
모듈을 자기 컴퓨터에 설치해 뒀다면 작동하겠지만 라이브러리 특성상 사용자가 설치하게 끔 하는 것은 올바른 방향은 아니다.
다음 플러그인을 설치한다.
npm i -D rollup-plugin-node-resolve
롤업 설정 파일에 추가하자
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import resolve from "rollup-plugin-node-resolve"; // 불러와서
import packageJSON from "./package.json";
const input = "./src/index.js";
export default [
// CommonJS
{
input,
output: {
file: packageJSON.main,
format: "cjs"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
resolve(), // 여기 추가
commonjs()
]
}
];
이렇게 하고 npm run build
를 하면 아직 외부 라이브러리를 불러올 수 없어서 에러가 발생한다.
프로젝트를 생성할 때 React와 Emotion을 peerDependencies
로 추가했었다. 이게 의미하는 바는 의도적으로 사용자가 직접 설치해야 함을 의미한다. 이런 모듈은 번들에 끼워넣기에는 알맞지 않기 때문에 그렇게 한 것이다.
npm i -D rollup-plugin-peer-deps-external
이 플러그인을 설치하면 package.json
에 정의된 peerDependencies
를 자동으로 Rollup external
설정에 추가해준다. Rollup 설정파일을 다음처럼 바꿔주자
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import resolve from "rollup-plugin-node-resolve";
import external from "rollup-plugin-peer-deps-external"; // 불러온다
import packageJSON from "./package.json";
const input = "./src/index.js";
export default [
// CommonJS
{
input,
output: {
file: packageJSON.main,
format: "cjs"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(), // 추가
resolve(),
commonjs()
]
}
];
이제 제대로 빌드가 될 것이다. 이 상태에서 publish한 경우 프로젝트 이름으로 import하여 만들어 놨던 버튼을 참조가 가능하다.
변경점이 있을 때마다 계속 다시 빌드하고 publish하기는 번거롭다. 그럴 때는 link
라는 커맨드를 사용한다. 라이브러리 프로젝트 폴더가 ~/ComponentLibrary
라고 하자. 그리고 다른 프로젝트에서 이를 사용하고자 할때 이런식으로 사용할 수 있다.
링크할 프로젝트를 지정한다.
cd ~/ComponentLibrary
yarn link
어디에서 사용할 건지 지정한다.
cd ~/project
yarn link "프로젝트 이름"
이렇게 하면 만들어진 번들 파일을 원하는 프로젝트에서 사용 가능하다. 만들어진 번들 파일을 사용하기 때문에 라이브러리에 변화가 생겼다면 다시 빌드 해야한다.
rollup-plugin-uglify
는 Rollup 2버전과 호환이 되지 않는다.
rollup-plugin-terser
를 이용한다.
npm i -D rollup-plugin-terser
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import resolve from "rollup-plugin-node-resolve";
import external from "rollup-plugin-peer-deps-external";
import { terser } from "rollup-plugin-terser";
import packageJSON from "./package.json";
const input = "./src/index.js";
const minifyExtension = pathToFile => pathToFile.replace(/\.js$/, ".min.js");
export default [
// CommonJS
{
input,
output: {
file: packageJSON.main,
format: "cjs"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs()
]
},
{
input,
output: {
file: minifyExtension(packageJSON.main),
format: "cjs"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs(),
terser()
]
}
];
모듈 시스템에 대해서는 [[JavaScript Module Systems]]를 참조.
다양한 모듈 시스템에 호환을 제공해 주기 위해 여러 버전으로 빌드할 수 있다.
package.json
을 다음처럼 바꿔주자.
{
...
"main": "lib/index.js",
"browser": "lib/index.umd.js",
"module": "lib/index.es.js",
...
}
rollup.config.js
에 UMD, ES 용 내용 추가
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import resolve from "rollup-plugin-node-resolve";
import external from "rollup-plugin-peer-deps-external";
import { terser } from "rollup-plugin-terser";
import packageJSON from "./package.json";
const input = "./src/index.js";
const minifyExtension = pathToFile => pathToFile.replace(/\.js$/, ".min.js");
export default [
// CommonJS
{
input,
output: {
file: packageJSON.main,
format: "cjs"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs()
]
},
{
input,
output: {
file: minifyExtension(packageJSON.main),
format: "cjs"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs(),
terser()
]
},
// UMD
{
input,
output: {
file: packageJSON.browser,
format: "umd",
name: "reactSampleComponentsLibrary",
globals: {
react: "React",
"@emotion/styled": "styled",
"@emotion/styled/base": "styled-base",
"@emotion/react": "css"
}
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs()
]
},
{
input,
output: {
file: minifyExtension(packageJSON.browser),
format: "umd",
name: "reactSampleComponentsLibrary",
globals: {
react: "React",
"@emotion/styled": "styled",
"@emotion/styled/base": "styled-base",
"@emotion/react": "css"
}
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs(),
terser()
]
},
// ES
{
input,
output: {
file: packageJSON.module,
format: "es",
exports: "named"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs()
]
},
{
input,
output: {
file: minifyExtension(packageJSON.module),
format: "es",
exports: "named"
},
plugins: [
babel({
exclude: "node_modules/**"
}),
external(),
resolve(),
commonjs(),
terser()
]
}
];
package.json
에 다음 스크립트를 추가하자.
"prepublishOnly": "rm -rf lib && npm run build",
"postbuild": "npm pack && tar -xvzf *.tgz && rm -rf package *.tgz"
첫번째 스크립트는 빌드 폴더를 지우고 다시 빌드 한다. 두번째 스크립트는 어떤 파일이 NPM에 publish될 것인지 보여준다.
참고로 package.json
의 name
필드는 고유해야한다.
이제 npm에 퍼블리싱 해보자
npm login
정보를 입력하고 로그인 한다.
npm publish
모든게 제대로 되었다면 npmjs.com/package/<패키이지름>
으로 게시되었을 것이다.
그런데 파일 목록을 보면 프로젝트 폴더 내 모든 파일이 등록되어있다. 우리는 번들된 파일만 보여주면 된다.
package.json
에 files
옵션을 추가하자
{
...
"files": [
"/lib"
],
...
}
마지막으로 필요한 경우 Rollup 설정에 output.sourcemap
에 true
를 설정하여 소스맵을 추가하자.
@emotion/styled
를 사용할 때 참고일부 Emotion기능을 위해서는 babel-plugin-emotion
이 필요할 수 있다.